package org.cll.lagou.study.imvc.servlet;

import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.cll.lagou.study.imvc.annotation.*;
import org.cll.lagou.study.imvc.config.AjaxResult;
import org.cll.lagou.study.imvc.config.HandlerMethod;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 描述信息:
 *
 * @author CLL
 * @version 1.0
 * @date 2020/6/18 22:23
 */
public class IDispatcherServlet extends HttpServlet {

    private static final Logger logger = LoggerFactory.getLogger(IDispatcherServlet.class);

    private static final long serialVersionUID = -2333018733587641120L;

    private Properties configProperties = new Properties();

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("UTF-8");
        resp.setCharacterEncoding("UTF-8");
        logger.info("====== 进行业务请求的处理");
//        String requestURI = req.getRequestURI();
//        Method method = handlerMapping.get(requestURI);
//        method.invoke()

        HandlerMethod handler = getHandler(req);
        if (null == handler) {
            resp.getWriter().print("unknown uri 404");
            return;
        }

        String securityName = handler.getSecurityName();
        if (StringUtils.isNotBlank(securityName)) {
            String name = req.getParameter("name");
            if (!securityName.equals(name)) {
                AjaxResult ajaxResult = new AjaxResult();
                ajaxResult.setSuccess(false);
                ajaxResult.setInfo("no authorization,permission denied!");
                resp.getWriter().print(ajaxResult.toString());
                return;
            }
        }

        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        logger.info("parameterTypes.length = {}", parameterTypes.length);
        int parameterCount = handler.getMethod().getParameterCount();
        logger.info("parameterCount = {}", parameterCount);

        Map<String, Integer> paramIndexMapping = handler.getParamIndexMapping();
        logger.info("paramIndexMapping.size = {}", paramIndexMapping.size());

        Object[] args = new Object[paramIndexMapping.size()];

        Map<String, String[]> parameterMap = req.getParameterMap();
        for (Map.Entry<String, String[]> entry : parameterMap.entrySet()) {
            if (paramIndexMapping.containsKey(entry.getKey())) {
                Integer integer = paramIndexMapping.get(entry.getKey());
                String value = StringUtils.join(entry.getValue(), ",");
                args[integer] = value;
            }
        }

        if (paramIndexMapping.containsKey(HttpServletRequest.class.getSimpleName())) {
            Integer integer = paramIndexMapping.get(HttpServletRequest.class.getSimpleName());
            args[integer] = req;
        }

        if (paramIndexMapping.containsKey(HttpServletResponse.class.getSimpleName())) {
            Integer integer = paramIndexMapping.get(HttpServletResponse.class.getSimpleName());
            args[integer] = resp;
        }

        try {
            Object invoke = handler.getMethod().invoke(handler.getController(), args);
            if (invoke instanceof AjaxResult) {
                AjaxResult result = (AjaxResult) invoke;
                resp.getWriter().print(result.toString());
                return;
            } else {
                resp.getWriter().print("未处理的返回结果类型");
                return;
            }
        } catch (Exception e) {
            logger.error("****** 执行抛出异常" + e.getMessage(), e);
            resp.getWriter().print("error");
            return;
        }
    }

    private HandlerMethod getHandler(HttpServletRequest request){
        if (CollectionUtils.isEmpty(handlerMapping)) {
            return null;
        }

        String requestURI = request.getRequestURI();
        String contextPath = request.getServletContext().getContextPath();
        if (StringUtils.isNotBlank(contextPath)) {
            requestURI = requestURI.substring(contextPath.length());
        }
//        String requestURL = request.getRequestURL().toString();

        for (HandlerMethod handlerMethod : handlerMapping) {
            if (handlerMethod.getUrl().matcher(requestURI).matches()) {
                return handlerMethod;
            }
        }

        return null;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        logger.info("====== 初始化自定义MVC框架");
        // 1.获取客户端传入的配置文件、读取配置文件为properties
        String contextConfigLocation = config.getInitParameter("contextConfigLocation");
        doLoadConfig(contextConfigLocation);

        // 3.扫描包下面的Bean
        doScan(configProperties.getProperty("scanPackage"));
        // 4.bean实例化
        doInstance();
        // 5.依赖注入
        doAutowired();
        // 6.配置url和method的映射关系
        initHandlerMapping();

        logger.info("====== ioc 容器初始化完成");
    }

//    private Map<String, Method> handlerMapping = new HashMap<>();
    private List<HandlerMethod> handlerMapping = new ArrayList<>();

    private void initHandlerMapping() {
        if (MapUtils.isEmpty(ioc)) {
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Class<?> aClass = entry.getValue().getClass();
            if (!aClass.isAnnotationPresent(IController.class)) {
                continue;
            }


            String classUrl = "";
            if (aClass.isAnnotationPresent(IRequestMapping.class)) {
                classUrl = aClass.getAnnotation(IRequestMapping.class).value();
            }

            Method[] methods = aClass.getMethods();
            if (methods.length > 0) {
                for (Method method : methods) {
                    if (method.isAnnotationPresent(IRequestMapping.class)) {
                        String methodUrl = method.getAnnotation(IRequestMapping.class).value();
                        String methodSecurity = method.getAnnotation(ISecurity.class).value();

                        HandlerMethod handlerMethod = new HandlerMethod(entry.getValue(), method, Pattern.compile(classUrl + methodUrl), methodSecurity);



                        Parameter[] parameters = method.getParameters();

                        for (int j = 0; j < parameters.length; j++) {
                            Parameter parameter = parameters[j];
                            if (HttpServletRequest.class == parameter.getType() || HttpServletResponse.class == parameter.getType()) {
                                handlerMethod.getParamIndexMapping().put(parameter.getType().getSimpleName(), j);
                            } else {
                                handlerMethod.getParamIndexMapping().put(parameter.getName(), j);
                            }
                        }

                        handlerMapping.add(handlerMethod);

//                        handlerMapping.put(classUrl + methodUrl, method);
                    }
                }
            }

        }
    }

    private void doAutowired() {
        if (MapUtils.isEmpty(ioc)) {
            return;
        }

        for (Map.Entry<String, Object> entry : ioc.entrySet()) {
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();
            if (0 == declaredFields.length) {
                continue;
            }

            for (Field field : declaredFields) {
                if (!field.isAnnotationPresent(IAutowired.class)) {
                    continue;
                }

                IAutowired iAutowired = field.getAnnotation(IAutowired.class);
                String beanName = iAutowired.value();
                if (StringUtils.isBlank(beanName.trim())) {
                    beanName = field.getType().getName();
                }

                field.setAccessible(true);
                try {
                    field.set(entry.getValue(), ioc.get(beanName));
                } catch (IllegalAccessException e) {
                    logger.error("****** 自动注入抛出异常" + e.getMessage(), e);
                    e.printStackTrace();
                }
            }
        }
    }

    private Map<String, Object> ioc = new HashMap<>();

    private void doInstance() {
        if (CollectionUtils.isEmpty(classNameSet)) {
            return;
        }

        for (String className : classNameSet) {
            try {
                Class<?> aClass = Class.forName(className);
                if (aClass.isAnnotationPresent(IController.class)) {
                    String beanName = lowerFirst(aClass.getSimpleName());
                    ioc.put(beanName, aClass.newInstance());
//                    ioc.put(beanName, aClass.getDeclaredConstructor().newInstance());
                } else if (aClass.isAnnotationPresent(IService.class)) {
                    IService iService = aClass.getAnnotation(IService.class);
                    if (StringUtils.isNotBlank(iService.value().trim())) {
                        String beanName = iService.value().trim();
                        ioc.put(beanName, aClass.newInstance());
                    } else {
                        String beanName = lowerFirst(aClass.getSimpleName());
                        ioc.put(beanName, aClass.newInstance());
                    }

                    Class<?>[] interfaces = aClass.getInterfaces();
                    if (null != interfaces && interfaces.length > 0) {
                        for (Class<?> interfaceClass : interfaces) {
                            ioc.put(interfaceClass.getName(), aClass.newInstance());
                        }
                    }
                }

            } catch (ClassNotFoundException e) {
                logger.error("****** 加载类失败 = {}", className);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
                logger.error("****** 反射实例化对象错误1 = {}", className);
            } catch (InstantiationException e) {
                e.printStackTrace();
                logger.error("****** 反射实例化对象错误2 = {}", className);
            }
//            catch (NoSuchMethodException e) {
//                e.printStackTrace();
//                logger.error("****** 反射实例化对象错误3 = {}", className);
//            } catch (InvocationTargetException e) {
//                e.printStackTrace();
//                logger.error("****** 反射实例化对象错误4 = {}", className);
//            }
        }
    }

    private String lowerFirst(String str){
        if (StringUtils.isBlank(str)) {
            return "";
        }

        char[] chars = str.toCharArray();
        if ('A' <= chars[0] && chars[0] <= 'Z') {
            chars[0] += 32;
        }

        return String.valueOf(chars);
    }


    private Set<String> classNameSet = new HashSet<>();

    private void doScan(String scanPackage) {
        String scanPackagePath = Thread.currentThread().getContextClassLoader().getResource("").getPath() + scanPackage.replaceAll("\\.", "/");

        File basePackageFile = new File(scanPackagePath);
        if (basePackageFile.exists()) {
            if (basePackageFile.isDirectory()) {
                File[] files = basePackageFile.listFiles();
                if (null == files) {
                    return;
                }
                for (File file : files) {
                    if (file.isDirectory()) {
                        doScan(scanPackage + "." + file.getName());
                    } else if (file.getName().endsWith(".class")) {
                        logger.info("****** 找到需要扫描的类 = {}", scanPackage + "." + file.getName());
                        String className = scanPackage + "." + file.getName().replaceAll(".class", "");
                        classNameSet.add(className);
                    } else {
                        logger.info("****** 不需要扫描 = {}", scanPackage + "." + file.getName());
                    }
                }
            } else if (basePackageFile.getName().endsWith(".class")) {
                logger.error("****** 找到需要扫描的类 = {}", scanPackage + "." + basePackageFile.getName());
                String className = scanPackage + "." + basePackageFile.getName().replaceAll(".class", "");
                classNameSet.add(className);
            } else {
                logger.info("****** 不需要扫描 = {}", scanPackage + "." + basePackageFile.getName());
            }


        } else {
            logger.error("****** 不存在当前文件或目录 = {}", scanPackagePath);
        }
    }

    private void doLoadConfig(String configPath) {
        InputStream resourceAsStream = IDispatcherServlet.class.getClassLoader().getResourceAsStream(configPath);
//        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream(configPath);
        try {
            configProperties.load(resourceAsStream);
        } catch (Exception e) {
            logger.error("****** 读取配置文件抛出异常" + e.getMessage(), e);
        }
    }
}
